﻿// ============================================================================
//  _ __   ___  ___  ___ _ __ ___  _   _
// | '_ \ / _ \/ __|/ _ \ '_ ` _ \| | | |
// | | | |  __/\__ \  __/ | | | | | |_| |
// |_| |_|\___||___/\___|_| |_| |_|\__,_|
//
// NES Emulator by daxnet, 2024
// MIT License
// ============================================================================

using System;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Krypton.Navigator;
using Krypton.Toolkit;
using NesEmu.Core;
using NesEmu.Explorer.Properties;

namespace NesEmu.Explorer.Pages;

public partial class FileBrowserPage : UserControl
{

    #region Private Fields

    private readonly FrmMain? _parent;

    #endregion Private Fields

    #region Public Constructors

    public FileBrowserPage(FrmMain parent)
        : this()
    {
        _parent = parent;
    }

    #endregion Public Constructors

    #region Private Constructors

    private FileBrowserPage()
    {
        InitializeComponent();
    }

    #endregion Private Constructors

    #region Public Methods

    public void LoadFilesFromDirectory(string path)
    {
        foreach (var romFile in Directory.EnumerateFiles(path, "*.nes", SearchOption.AllDirectories))
            try
            {
                var romFileInfo = new FileInfo(romFile);
                var lvi = new ListViewItem(Path.GetFileName(romFile));
                lvi.ImageKey = @"game-monitor.png";
                lvi.SubItems.Add($"{romFileInfo.Length / 1024:N0} KB");
                var cartridge = new Cartridge(romFile);
                var mapperRegistry = Emulator.GetRegisteredMapperInfo()
                    .FirstOrDefault(mi => mi.Number == cartridge.Mapper);
                if (mapperRegistry is not null)
                {
                    lvi.ForeColor = SystemColors.ControlText;
                    lvi.Group = lv.Groups["lvgSupported"];
                }
                else
                {
                    lvi.ForeColor = Color.Gray;
                    lvi.Group = lv.Groups["lvgNotSupported"];
                }

                lvi.SubItems.Add(Path.GetDirectoryName(romFile));

                lvi.Tag = new FileBrowserItemTag { FilePath = romFile, Cartridge = cartridge };
                lv.Items.Add(lvi);
            }
            catch
            {
            }
    }

    #endregion Public Methods

    #region Private Methods

    private void kcOpenBinaryViewer_Execute(object sender, EventArgs e)
    {
        if (lv.SelectedItems.Count > 0 && lv.SelectedItems[0].Tag is FileBrowserItemTag selectedItemTag)
            OpenBinaryViewer(selectedItemTag);
    }

    private void kcOpenChrViewer_Execute(object sender, EventArgs e)
    {
        if (lv.SelectedItems.Count > 0 && lv.SelectedItems[0].Tag is FileBrowserItemTag selectedItemTag)
            OpenChrViewer(selectedItemTag);
    }

    private void kcOpenDasm_Execute(object sender, EventArgs e)
    {
        if (lv.SelectedItems.Count > 0 && lv.SelectedItems[0].Tag is FileBrowserItemTag selectedItemTag)
            OpenDisassembler(selectedItemTag);
    }

    private void lv_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
    {
        _parent?.Broadcast(this, "fileBrowser.itemSelected", e.Item?.Tag);
    }

    private void lv_MouseClick(object sender, MouseEventArgs e)
    {
        var selectedItem = lv.GetItemAt(e.X, e.Y);
        if (selectedItem != null)
        {
            selectedItem.Selected = true;
            if (e.Button == MouseButtons.Right) kryptonContextMenu.Show(lv);
        }
    }

    private void lv_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        var item = lv.GetItemAt(e.X, e.Y);
        if (item is not null && item.Tag is FileBrowserItemTag tag)
        {
            _parent?.Broadcast(this, "fileBrowser.itemDoubleClicked", tag);
            OpenBinaryViewer(tag);
        }
    }

    private void OpenBinaryViewer(FileBrowserItemTag selectedItemTag)
    {
        using (new LengthyOperation(this))
        {
            var nameSuffix = selectedItemTag.FilePath?.Replace(':', '_').Replace('/', '_').Replace("\\", "_");
            _parent?.AddOrShowWorkspacePage<HexViewerPage.Page>($"{HexViewerPage.Page.PageName}_{nameSuffix}",
                selectedItemTag);
        }
    }

    private void OpenChrViewer(FileBrowserItemTag selectedItemTag)
    {
        using (new LengthyOperation(this))
        {
            if (selectedItemTag.Cartridge?.ChrRomSize == 0)
            {
                KryptonMessageBox.Show("The selected cartridge doesn't have a CHR ROM.",
                    "Failed to open CHR Viewer",
                    KryptonMessageBoxButtons.OK, KryptonMessageBoxIcon.Application);
                return;
            }

            var nameSuffix = selectedItemTag.FilePath?.Replace(':', '_').Replace('/', '_').Replace("\\", "_");
            _parent?.AddOrShowWorkspacePage<ChrRomViewerPage.Page>($"{ChrRomViewerPage.Page.PageName}_{nameSuffix}",
                selectedItemTag);
        }
    }

    private void OpenDisassembler(FileBrowserItemTag selectedItemTag)
    {
        using (new LengthyOperation(this))
        {
            if (selectedItemTag.Cartridge is not null)
            {
                var mapperRegistry = Emulator.GetRegisteredMapperInfo()
                    .FirstOrDefault(mi => mi.Number == selectedItemTag.Cartridge.Mapper);
                if (mapperRegistry is null)
                {
                    KryptonMessageBox.Show($"Mapper {selectedItemTag.Cartridge.Mapper} is not supported.",
                        $"Failed to disassemble \"{Path.GetFileName(selectedItemTag.FilePath)}\"",
                        KryptonMessageBoxButtons.OK, KryptonMessageBoxIcon.Application);
                    return;
                }

                var nameSuffix = selectedItemTag.FilePath?.Replace(':', '_').Replace('/', '_').Replace("\\", "_");
                _parent?.AddOrShowWorkspacePage<DisassemblerPage.Page>($"{DisassemblerPage.Page.PageName}_{nameSuffix}",
                    selectedItemTag);
            }
        }
    }

    #endregion Private Methods

    #region Public Classes

    public class FileBrowserItemTag
    {

        #region Public Properties

        public Cartridge? Cartridge { get; set; }

        public string? FilePath { get; set; }

        #endregion Public Properties

    }

    public sealed class Page : KryptonPage
    {

        #region Public Fields

        public const string PageName = "F9B4282A-1BB4-4BC9-8695-094F7125AE39";

        #endregion Public Fields

        #region Private Fields

        private readonly FileBrowserPage _ctrl;

        #endregion Private Fields

        #region Public Constructors

        public Page(FrmMain parent, object? args = null)
            : base("File Browser", Resources.ListViewTable, PageName)
        {
            parent.OnMessageBroadcasted += Parent_OnMessageBroadcasted;
            TextTitle = @"File Browser";
            TextDescription = TextTitle;
            _ctrl = new FileBrowserPage(parent)
            {
                Dock = DockStyle.Fill
            };

            Controls.Add(_ctrl);
        }

        #endregion Public Constructors

        #region Private Methods

        private void Parent_OnMessageBroadcasted(object sender, BroadcastEventArgs e)
        {
            switch (e.Message)
            {
                case "main.directoryOpen":
                    if (e.Args is null) throw new ArgumentNullException(nameof(e.Args), "Directory is not specified.");
                    _ctrl.LoadFilesFromDirectory(e.Args.ToString());
                    break;
            }
        }

        #endregion Private Methods

    }

    #endregion Public Classes
}